/**
 * @author        Nicolas Bottarini <nicolasbottarini@gmail.com>, 404 <http://www.proyecto404.com>
 * @version       $Rev: 3 $
 * @lastrevision  $Date: 2011-09-12 16:45:37 +0000 (Mon, 12 Sep 2011) $	 
 */
package com.proyecto404.core {
	import com.proyecto404.core.collections.Dictionary;
	import com.proyecto404.core.collections.IIterable;
	import com.proyecto404.core.collections.IIterator;
	import com.proyecto404.core.collections.ISet;
	import com.proyecto404.core.collections.Queue;
	import com.proyecto404.core.collections.Set;
	import com.proyecto404.core.configuration.ConfigurationHandler;
	import com.proyecto404.core.io.FileLoadedEventArgs;
	import com.proyecto404.core.io.FileLoader;
	import com.proyecto404.core.io.MultiFileLoader;
	import com.proyecto404.core.util.ClassHelper;
	import com.proyecto404.core.util.StringHelper;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;
	
	import flashx.textLayout.tlf_internal;
	
	use namespace flash_proxy;
	
	public class Configuration extends Proxy {
		private var _handlers:Dictionary;
		private var _externals:Dictionary;
		private var _loadConfigurationJobs:JobQueue;
		private var _multiLoader:MultiFileLoader;
		private var _configurationPath:String;

		public function Configuration(configurationPath:String) {
			_handlers = new Dictionary();
			_externals = new Dictionary();
			_loadConfigurationJobs = new JobQueue();
			_multiLoader = new MultiFileLoader();
			_configurationPath = configurationPath;
		}

		public function addConfigurationFile(configFile:String, configurationLoadedCallback:Function = null):void {
			// Add Job for loading configuration file
			var job:GenericJob = new GenericJob(function(callback:Function):void {
				var loader:FileLoader = new FileLoader();
				loader.loaded.addOnce(function(e:FileLoadedEventArgs):void {
					onConfigurationLoaded(e.data, function():void {
						// Notify Configuration Loaded
						if (configurationLoadedCallback != null) {
							configurationLoadedCallback.call(this);
						}
						
						// Notify Job Completion to Queue
						callback.call(null, new EventArgs()); 
					});
				});
				loader.load(_configurationPath + configFile); 
			});
			
			_loadConfigurationJobs.addJob(job);
		}
		
		public function loadConfiguration(configurationFiles:ISet = null, configurationLoadedCallback:Function = null):void {
			if (configurationFiles != null) {
				var it:IIterator = configurationFiles.getIterator();
				while(it.hasNext()) {
					addConfigurationFile(it.next());
				}
			}
			
			if (configurationLoadedCallback != null) {
				_loadConfigurationJobs.allJobsExecuted.addOnce(configurationLoadedCallback);
			}
			
			_loadConfigurationJobs.execute();
		}
		
		private function onConfigurationLoaded(data:XML, executedCallback:Function):void {
			Check.argument.isNotNull(data, "data");
			// Load Handlers
			for each(var handler:XML in data.handlers.handler) {
				loadHandler((handler.@name).toString(), (handler.@className).toString());
			}
			
			var externals:Set = new Set();

			for each(var node:XML in data.children()) {
				var name:String = node.name().toString();
				if (name == "handlers") {
					continue;
				}
				
				if (name == "external") {
					externals.add(node);
					continue;
				}
				
				loadConfigurationNode(node);
			}
			
			if (!externals.isEmpty()) {
				loadExternals(externals, executedCallback);
			} else {
				executedCallback.call(this);
			}
		}
		
		private function loadConfigurationNode(node:XML):void {
			var name:String = node.name().toString();
			
			if (!_handlers.containsKey(name)) {
				throw new Exception("There is no configuration handler for section " + name);
			}
			
			var ch:ConfigurationHandler = _handlers.get(name) as ConfigurationHandler;
			ch.addConfigurations(node);
		}
		
		private function loadExternals(externals:ISet, executedCallback:Function):void {
			var it:IIterator = externals.getIterator();
			while (it.hasNext()) {
				var external:XML = it.next();
				_multiLoader.addFile(_configurationPath + external.@file, function(e:FileLoadedEventArgs):void {
					loadConfigurationNode(e.data);					
				});
			}
			
			_multiLoader.allLoaded.addOnce(function(e:EventArgs):void {
				executedCallback.call(this);
			});
			
			_multiLoader.loadAll();
		}
		
		private function loadHandler(name:String, className:String):void {
			if(_handlers.containsKey(name)) {
				throw new Exception("Handler " + name + " already defined");
			}
			
			var handlerClass:Class = ClassHelper.getClassByName(className);
			_handlers.add(name, new handlerClass(name));
		}
		
		public function get(configuration:String):* {
			var configurationParts:Array = configuration.split(":");
			var handler:String = StringHelper.trim(configurationParts[0]);
			if (!_handlers.containsKey(handler)) {
				throw new Exception("Invalid configuration handler: " + handler);	
			}
			if (configurationParts[1] != null) {
				configuration =  StringHelper.trim(configurationParts[1]);
				if (!ConfigurationHandler(_handlers.get(handler)).contains(configuration)) {
					throw new Exception("Invalid Configuration " + configuration);
				}
				return ConfigurationHandler(_handlers.get(handler)).get(configuration);
			} else {
				return ConfigurationHandler(_handlers.get(handler)).getConfigurations();
			}
		}
		
		public function hasConfiguration(configuration:String):Boolean {
			var configurationParts:Array = configuration.split(":");
			var handler:String = StringHelper.trim(configurationParts[0]);
			if (!_handlers.containsKey(handler)) {
				// throw new Exception("Invalid configuration handler: " + handler);
				return false;
			}
			if (configurationParts[1] != null) {
				configuration =  StringHelper.trim(configurationParts[1]);
				return ConfigurationHandler(_handlers.get(handler)).contains(configuration);
			} else {
				throw new Exception("Invalid configuration: " + configuration);
			}
		}
		
		public function hasSection(section:String):Boolean {
			return _handlers.containsKey(section);
		}

		override flash_proxy function getProperty(name:*):* {
			return get(name);
		}
	}
}

class ConfigurationLoadJob {
	public function ConfigurationLoadJob(file:String, callback:Function) {
		
	}	
}